/*
 *   Copyright 2021 zzh
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.aduib.boot.common.compiler.support;

import com.aduib.boot.common.util.ClassUtils;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FixedValue;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @description: ByteBuddyClassBuilder
 * @author: zzh
 * @date: 2021/10/18 10:29
 */
@Deprecated
public class ByteBuddyClassBuilder {

  private final List<Class<?>> interfaces = new ArrayList<>();
  private final List<ConstructorDescriptor> constructorDescriptorList = new ArrayList<>();
  private final List<FieldDescriptor> fieldDescriptorList = new ArrayList<>();
  private final List<MethodDescriptor> methodDescriptorList = new ArrayList<>();
  /** import导入信息，类名对应全类名 */
  private Map<String, String> fullNames = new HashMap<>();
  private String className;
  private Class<?> superClass = Object.class;

  public void addInterface(Class<?> interfaceClass) {
    this.interfaces.add(interfaceClass);
  }

  public void addImports(String pkg) {
    int pi = pkg.lastIndexOf('.');
    if (pi > 0) {
      String pkgName = pkg.substring(0, pi);
      if (!pkg.endsWith(".*")) {
        fullNames.put(pkg.substring(pi + 1), pkg);
      }
    }
  }

  public Class<?> getImportsClass(String simpleClassName) {
    try {
      String className = fullNames.get(simpleClassName);
      if (className == null) {
        return null;
      }
      return ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
    } catch (Exception ignore) {
      return null;
    }
  }

  public String getClassName() {
    return className;
  }

  public void setClassName(String className) {
    this.className = className;
  }

  public Class<?> getSuperClass() {
    return superClass;
  }

  public void setSuperClass(Class<?> superClass) {
    this.superClass = superClass;
  }

  public List<Class<?>> getInterfaces() {
    return interfaces;
  }

  /**
   * 返回加载的class
   *
   * @param classLoader
   * @return
   */
  public Class<?> build(ClassLoader classLoader) throws IOException {
    ByteBuddy byteBuddy = new ByteBuddy();
    DynamicType.Builder<?> builder =
        byteBuddy.subclass(superClass).name(className).implement(interfaces);
    for (ConstructorDescriptor constructorDescriptor : constructorDescriptorList) {
      builder =
          builder
              .defineConstructor(constructorDescriptor.getModifier())
              .withParameters(constructorDescriptor.getParameterTypes())
              .throwing(constructorDescriptor.getThrowables())
              .intercept(constructorDescriptor.getImplementation());
    }
    for (FieldDescriptor fieldDescriptor : fieldDescriptorList) {
      builder =
          builder.defineField(
              fieldDescriptor.getFieldName(),
              fieldDescriptor.getFieldType(),
              fieldDescriptor.getModifier());
      if (Objects.nonNull(fieldDescriptor.getByteCodeAppender())) {
        builder = builder.initializer(fieldDescriptor.getByteCodeAppender());
      }
    }
    for (MethodDescriptor methodDescriptor : methodDescriptorList) {
      builder =
          builder
              .defineMethod(
                  methodDescriptor.getMethodName(),
                  methodDescriptor.getReturnType(),
                  methodDescriptor.getModifier())
              .withParameters(methodDescriptor.getParameterTypes())
              .throwing(methodDescriptor.getThrowables())
              .intercept(FixedValue.nullValue());
    }
    DynamicType.Unloaded<?> dynamicType = builder.make();
    dynamicType.saveIn(new File("/Users/zzh/SumExample.txt"));
    return dynamicType.load(classLoader).getLoaded();
  }

  public ConstructorDescriptor buildConstructorDescriptor() {
    return new ConstructorDescriptor();
  }

  public FieldDescriptor buildFieldDescriptor() {
    return new FieldDescriptor();
  }

  public MethodDescriptor buildMethodDescriptor() {
    return new MethodDescriptor();
  }

  public void addConstructorDescriptor(ConstructorDescriptor constructorDescriptor) {
    constructorDescriptorList.add(constructorDescriptor);
  }

  public void addFieldDescriptor(FieldDescriptor fieldDescriptor) {
    fieldDescriptorList.add(fieldDescriptor);
  }

  public void addMethodDescriptor(MethodDescriptor methodDescriptor) {
    methodDescriptorList.add(methodDescriptor);
  }

  static class ConstructorDescriptor {

    private final List<Class<?>> parameterTypes = new ArrayList<>();
    private final List<Class<?>> throwables = new ArrayList<>();
    private Visibility modifier = Visibility.PUBLIC;
    private Implementation implementation = FixedValue.nullValue();

    public ConstructorDescriptor addParameterType(Class<?> parameterType) {
      this.parameterTypes.add(parameterType);
      return this;
    }

    public ConstructorDescriptor addThrowable(Class<?> throwable) {
      this.throwables.add(throwable);
      return this;
    }

    public Visibility getModifier() {
      return modifier;
    }

    public ConstructorDescriptor setModifier(Visibility modifier) {
      this.modifier = modifier;
      return this;
    }

    public List<Class<?>> getParameterTypes() {
      return parameterTypes;
    }

    public List<Class<?>> getThrowables() {
      return throwables;
    }

    public Implementation getImplementation() {
      return implementation;
    }

    public ConstructorDescriptor setImplementation(Implementation implementation) {
      this.implementation = implementation;
      return this;
    }
  }

  static class MethodDescriptor extends ConstructorDescriptor {

    private String methodName;

    private Class<?> returnType;

    public String getMethodName() {
      return methodName;
    }

    public MethodDescriptor setMethodName(String methodName) {
      this.methodName = methodName;
      return this;
    }

    public Class<?> getReturnType() {
      return returnType;
    }

    public MethodDescriptor setReturnType(Class<?> returnType) {
      this.returnType = returnType;
      return this;
    }
  }

  static class FieldDescriptor {

    private String fieldName;

    private Class<?> fieldType;

    private Visibility modifier = Visibility.PUBLIC;

    private ByteCodeAppender byteCodeAppender = null;

    public String getFieldName() {
      return fieldName;
    }

    public FieldDescriptor setFieldName(String fieldName) {
      this.fieldName = fieldName;
      return this;
    }

    public Class<?> getFieldType() {
      return fieldType;
    }

    public FieldDescriptor setFieldType(Class<?> fieldType) {
      this.fieldType = fieldType;
      return this;
    }

    public Visibility getModifier() {
      return modifier;
    }

    public FieldDescriptor setModifier(Visibility modifier) {
      this.modifier = modifier;
      return this;
    }

    public ByteCodeAppender getByteCodeAppender() {
      return byteCodeAppender;
    }

    public void setByteCodeAppender(ByteCodeAppender byteCodeAppender) {
      this.byteCodeAppender = byteCodeAppender;
    }
  }
}
